package com.bac.rentmap.ad.flat;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Text;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class FlatAd implements Serializable {
    public static final String DISTANCE_PROPERTY_PREFIX = "_dist";

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    public void setId(Long id) {
        this.id = id;
    }

    @Persistent
    private String city;

    @Persistent
    private String street;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private String streetPrefix;

    @Persistent
    private Integer building;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private String buildingPart;

    @Persistent
    private Boolean buildingDefined = false;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private Integer price;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private Boolean pricePerDay;

    @Persistent
    private String phone;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private Date firstPublishingDate;

    @Persistent
    private Date lastPublishingDate;

    @Persistent
    private Long rating;

    private Boolean ratingCalculated = false;

    @Persistent
    private Boolean distanceCalculated = false;

    @Persistent
    private String room;

    @Persistent
    private Text description;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private Boolean agent;

    @Persistent
    private Boolean agentCalculated = false;

    @Persistent
    private String sourceUrl;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private String externalUrl;
    
    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private List<String> externalImages;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private Integer agentUserReviewCount;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private Integer badPhoneUserReviewCount;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private Integer expiredAdUserReviewCount;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    public Double lat;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    public Double lon;

    public static final String STATUS_NOT_CALCULATED = "not_calculated";
    public static final String STATUS_CALCULATED = "calculated";
    public static final String STATUS_ERROR = "error";
    @Persistent
    private String geoLocationStatus = STATUS_NOT_CALCULATED;

    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private List<String> priceRanges;

    /**
     * Do not rely on this field very much. It is used for performance reasons
     * only during search result displaying.
     */
    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private List<Key> flatImages;

    private LocalAddress localAddress;

    /**
     * Is provided to ad owner.
     */
    @Persistent
    private String secureId;
    
    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private String email;
    
    @Persistent
    @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true")
    private Set<String> reviewers;

    // true if price >= threshold, otherwise false
    public static final String PRICE_GREATER_OR_EQUAL_PREFIX = "g";

    public static final String PRICE_LESS_OR_EQUAL_PREFIX = "l";

    public static final String PRICE_RANGE_SEPARATOR = "_";

    public static final int PRICE_THRESHOLD_STEP = 50;

    public static final int MIN_PRICE_THRESHOLD_STEP_MULTIPLIER = 2;

    public static final int MAX_PRICE_THRESHOLD_STEP_MULTIPLIER = 10;

    public Entity convert2entity() {
        Entity res;
        if (id != null) {
            res = new Entity(KeyFactory.createKey(getClass().getSimpleName(),
                    id));
        } else {
            res = new Entity(getClass().getSimpleName());
        }
        res.setProperty("city", city);
        res.setProperty("street", street);
        res.setUnindexedProperty("streetPrefix", streetPrefix);
        res.setProperty("building", building);
        res.setUnindexedProperty("buildingPart", buildingPart);
        res.setProperty("buildingDefined", buildingDefined);
        res.setProperty("price", price);
        res.setProperty("pricePerDay", pricePerDay != null && pricePerDay);

        res.setProperty("phone", phone);
        res.setUnindexedProperty("firstPublishingDate", new Date(firstPublishingDate.getTime()));
        res.setProperty("lastPublishingDate", new Date(lastPublishingDate.getTime()));
        if (rating != null) {
            res.setProperty("rating", rating);
        }
        res.setProperty("ratingCalculated", ratingCalculated);
        res.setProperty("priceRanges", priceRanges);
        if (lat != null) {
            res.setProperty("lat", lat);
        }
        if(lon != null){
            res.setProperty("lon", lon);
        }
        res.setProperty("distanceCalculated", distanceCalculated);
        res.setProperty("geoLocationStatus", geoLocationStatus);
        res.setProperty("room", room);
        res.setUnindexedProperty("description", description);
        res.setProperty("agent", agent);
        res.setProperty("agentCalculated", agentCalculated);
        if (agentUserReviewCount != null) {
            res.setUnindexedProperty("agentUserReviewCount", agentUserReviewCount);
        }
        if (badPhoneUserReviewCount != null) {
            res.setUnindexedProperty("badPhoneUserReviewCount", badPhoneUserReviewCount);
        }
        if (expiredAdUserReviewCount != null) {
            res.setUnindexedProperty("expiredAdUserReviewCount",
                    expiredAdUserReviewCount);
        }
        if (secureId != null) {
            res.setProperty("secureId", secureId);
        }
        if(email != null){
          res.setUnindexedProperty("email", email);
        }
        if (flatImages != null && !flatImages.isEmpty()) {
            res.setUnindexedProperty("flatImages", flatImages);
        }
        if(reviewers != null && !reviewers.isEmpty()){
            res.setUnindexedProperty("reviewers", reviewers);
        }
        if(sourceUrl != null){
            res.setProperty("sourceUrl",
                    sourceUrl);
        }
        if(externalUrl != null){
            res.setUnindexedProperty("externalUrl", externalUrl);
        }
        if(externalImages != null){
            res.setUnindexedProperty("externalImages", externalImages);
        }
        return res;
    }

    public Long getId() {
        return id;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public Integer getBuilding() {
        return building;
    }

    private void setBuilding(Integer building) {
        this.building = building;
        buildingDefined = building != null;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
        List<String> priceRanges = new ArrayList<String>();
        if (price != null) {
            for (int i = MIN_PRICE_THRESHOLD_STEP_MULTIPLIER; i < MAX_PRICE_THRESHOLD_STEP_MULTIPLIER; i++) {
                int minPrice = 50 * i;
                if (price >= minPrice) {
                    priceRanges.add(minPrice + PRICE_RANGE_SEPARATOR);
                    for (int j = i + 1; j <= MAX_PRICE_THRESHOLD_STEP_MULTIPLIER; j++) {
                        int maxPrice = 50 * j;
                        if (price <= maxPrice) {
                            priceRanges.add(minPrice + PRICE_RANGE_SEPARATOR
                                    + maxPrice);
                        }
                    }
                }
            }
            for (int j = MIN_PRICE_THRESHOLD_STEP_MULTIPLIER + 1; j <= MAX_PRICE_THRESHOLD_STEP_MULTIPLIER; j++) {
                int maxPrice = 50 * j;
                if (price <= maxPrice) {
                    priceRanges.add(PRICE_RANGE_SEPARATOR + maxPrice);
                }
            }
        }
        this.priceRanges = priceRanges;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public GeoLocation getGeoLocation() {
        if(lat != null && lon != null)
            return new GeoLocation(lat, lon);
        else
            return null;
    }

    public void setGeoLocation(GeoLocation geoLocation) {
        this.lat = geoLocation.lat;
        this.lon = geoLocation.lon;
        geoLocationStatus = STATUS_CALCULATED;
    }

    public Date getFirstPublishingDate() {
        return firstPublishingDate;
    }

    public void setFirstPublishingDate(Date firstPublishingDate) {
        this.firstPublishingDate = firstPublishingDate;
    }

    public Date getLastPublishingDate() {
        return lastPublishingDate;
    }

    public void setLastPublishingDate(Date lastPublishingDate) {
        if(lastPublishingDate == null){
            this.lastPublishingDate = null;
        }
        else {
            this.lastPublishingDate = new Date(lastPublishingDate.getTime());
        }
    }

    public long getRating() {
        return rating;
    }

    /**
     *
     *
     * @param rating
     *            null means that rating shall be recalculated
     */
    public void setRating(Long rating) {
        this.rating = rating;
        ratingCalculated = rating != null;
        distanceCalculated = false;
    }

    public void setGeoLocationError() {
        geoLocationStatus = STATUS_ERROR;
    }

    public void setRoom(String room) {
        this.room = room;
    }

    public List<String> getPriceRanges() {
        return priceRanges;
    }

    public void setPriceRanges(List<String> priceRanges) {
        this.priceRanges = priceRanges;
    }

    public void setAgent(Boolean agent) {
        this.agent = agent;
        agentCalculated = true;
        setRating(null);
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder(getClass().getSimpleName());
        res.append('[');
        res.append(id);
        res.append(',');
        res.append(getLocalAddress());
        res.append(',');
        res.append(phone);
        res.append(']');
        return res.toString();
    }

    public void setLocalAddress(LocalAddress localAddress) {
        this.localAddress = localAddress;
        LocalAddressPrefix localAddressPrefix = localAddress.getPrefix();
        if (localAddressPrefix != null)
            streetPrefix = localAddressPrefix.getName();
        street = localAddress.getStreet();
        setBuilding(localAddress.getBuilding());
        buildingPart = localAddress.getBuildingPart();
    }

    public LocalAddress getLocalAddress() {
        if (localAddress == null) {
            LocalAddressPrefix localAddressPrefix = null;
            if (streetPrefix != null) {
                localAddressPrefix = LocalAddressPrefix
                        .valueOfName(streetPrefix);
            }
            localAddress = new LocalAddress(localAddressPrefix, street,
                    building, buildingPart);
        }
        return localAddress;
    }

    public String getDescription() {
        if (description == null)
            return null;
        return description.getValue();
    }

    public void setDescription(String description) {
        this.description = new Text(description);
    }

    public Boolean getAgent() {
        return agent;
    }

    public String getSourceUrl() {
        return sourceUrl;
    }

    public void setSourceUrl(String sourceUrl) {
        this.sourceUrl = sourceUrl;
    }

    public Integer getAgentUserReviewCount() {
        return agentUserReviewCount;
    }

    public void setAgentUserReviewCount(Integer agentUserReviewCount) {
        this.agentUserReviewCount = agentUserReviewCount;
    }

    public Integer getBadPhoneUserReviewCount() {
        return badPhoneUserReviewCount;
    }

    public void setBadPhoneUserReviewCount(Integer badPhoneUserReviewCount) {
        this.badPhoneUserReviewCount = badPhoneUserReviewCount;
    }

    public Integer getExpiredAdUserReviewCount() {
        return expiredAdUserReviewCount;
    }

    public void setExpiredAdUserReviewCount(Integer expiredAdUserReviewCount) {
        this.expiredAdUserReviewCount = expiredAdUserReviewCount;
    }

    public void setRatingCalculated(Boolean ratingCalculated) {
        this.ratingCalculated = ratingCalculated;
    }

    public Boolean getRatingCalculated() {
        return ratingCalculated;
    }

    public String getSecureId() {
        return secureId;
    }

    public void setSecureId(String secureId) {
        this.secureId = secureId;
    }

    public String getRoom() {
        return room;
    }

    public List<Key> getFlatImages() {
        return flatImages;
    }

    public void setFlatImages(List<Key> flatImages) {
        this.flatImages = flatImages;
    }

    public Set<String> getReviewers() {
      return reviewers;
    }

    public void setReviewers(Set<String> reviewers) {
      this.reviewers = reviewers;
    }

    public String getEmail() {
      return email;
    }

    public void setEmail(String email) {
      this.email = email;
    }

    public Double getLat() {
        return lat;
    }

    public Double getLon() {
        return lon;
    }

    public Boolean getAgentCalculated() {
        return agentCalculated;
    }

    public String getExternalUrl() {
        return externalUrl;
    }
    
    public void setExternalUrl(String externalUrl) {
        this.externalUrl = externalUrl;
    }

    public List<String> getExternalImages() {
        return externalImages;
    }

    public void setExternalImages(List<String> externalImages) {
        this.externalImages = externalImages;
    }
    
    public boolean getExternal(){
        return externalUrl != null;
    }

    public Boolean getPricePerDay() {
        return pricePerDay;
    }

    public void setPricePerDay(Boolean pricePerDay) {
        this.pricePerDay = pricePerDay;
    }
}
